package com.yytek.tcp.netty.ModbusTcp;


import com.yytek.common.core.constant.CacheConstants;
import com.yytek.common.core.constant.ProtocolConstans;
import com.yytek.common.core.domain.DeviceConectInfo;
import com.yytek.common.redis.service.RedisService;
import com.yytek.gw.utils.DriverUtil;
import com.yytek.tcp.netty.tcp.NettyTcpServer;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.*;
import lombok.extern.slf4j.Slf4j;

import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


import javax.annotation.Resource;
import java.net.InetSocketAddress;
import java.net.SocketException;

/**
 * modbusTcp处理逻辑
 */
@Slf4j
@Component
@ChannelHandler.Sharable
public class NettyModBusServerHandler extends ChannelInboundHandlerAdapter {
    @Resource
    private RedisService redisService;
    @Value("${server.port}")
    private Integer serverPort;
    /**
     * 功能描述: 有客户端连接服务器会触发此函数
     * @param  ctx 通道
     * @return void
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws SocketException {
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = insocket.getAddress().getHostAddress();
        int clientPort = insocket.getPort();
        //TODO:获取连接通道唯一标识,暂定IP和端口
        String channelId = clientIp+clientPort;
        String deviceId = channelId;
        //如果map中不包含此连接，就保存连接
        if (NettyModBusTcpServer.biDirectionHashMap.containsKey(channelId)) {
            log.info("客户端:{},是连接状态，连接通道数量:{} ",channelId,NettyModBusTcpServer.biDirectionHashMap.size());
        } else {
            //保存连接
            //缓存key设备id
            String key = CacheConstants.GW_DEVICE_CONECT_KEY+deviceId;
            DeviceConectInfo value = DriverUtil.getConectCacheValue(deviceId,serverPort, ProtocolConstans.TCP);
            redisService.setCacheObject(key,value);
            NettyModBusTcpServer.biDirectionHashMap.put(channelId, ctx.channel());
            log.info("客户端:{},连接netty服务器[IP:{}-->PORT:{}]",channelId, clientIp,clientPort);
            log.info("连接通道数量: {}",NettyModBusTcpServer.biDirectionHashMap.size());
        }
    }

    /**
     * 功能描述: 有客户端终止连接服务器会触发此函数
     * @param  ctx 通道处理程序上下文
     * @return void
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = inSocket.getAddress().getHostAddress();
        int clientPort = inSocket.getPort();
        //TODO:获取连接通道唯一标识,暂定IP和端口
        String channelId = clientIp+clientPort;
        //包含此客户端才去删除
        if (NettyModBusTcpServer.biDirectionHashMap.containsKey(channelId)) {
            //删除连接
            removeChannelCache(ctx.channel());
            log.info("客户端:{},连接netty服务器[IP:{}-->PORT:{}]",channelId, clientIp,inSocket.getPort());
            log.info("连接通道数量: " + NettyModBusTcpServer.biDirectionHashMap.size());
        }
    }

    /**
     * 功能描述: 有客户端发消息会触发此函数
     * @param  ctx 通道处理程序上下文
     * @param  msg 客户端发送的消息
     * @return void
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("加载客户端报文,客户端id:{},客户端消息:{}",ctx.channel().id(),
                ByteBufUtil.hexDump((ByteBuf) msg));
        String data = String.valueOf(msg);
        Integer water = Integer.parseInt(data.substring(6,10),16);
        log.info("当前水位:{}cm",water);
        //响应客户端
        //this.channelWrite(ctx.channel().id(), msg);
    }



    /**
     * 功能描述: 服务端给客户端发送消息
     * @param  channelId 连接通道唯一id
     * @param  msg 需要发送的消息内容
     * @return void
     */
    public void channelWrite(String channelId, Object msg) throws Exception {
        Channel channel = NettyModBusTcpServer.biDirectionHashMap.getByKey(channelId);
        if (channel == null) {
            log.info("通道:{},不存在",channelId);
            return;
        }
        if (msg == null || msg == "") {
            log.info("服务端响应空的消息");
            return;
        }
        //将客户端的信息直接返回写入ctx
        channel.write(msg);
        //刷新缓存区
        channel.flush();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        String socketString = ctx.channel().remoteAddress().toString();
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.READER_IDLE) {
                log.info("Client:{},READER_IDLE 读超时",socketString);
                ctx.disconnect();
                Channel channel = ctx.channel();
                ChannelId id = channel.id();
                removeChannelCache(channel);
            } else if (event.state() == IdleState.WRITER_IDLE) {
                log.info("Client:{}, WRITER_IDLE 写超时",socketString);
                ctx.disconnect();
                Channel channel = ctx.channel();
                removeChannelCache(channel);
            } else if (event.state() == IdleState.ALL_IDLE) {
                log.info("Client:{},ALL_IDLE 总超时",socketString);
                ctx.disconnect();
                Channel channel = ctx.channel();
                removeChannelCache(channel);
            }
        }
    }

    /**
     * 功能描述: 发生异常会触发此函数
     * @param  ctx 通道处理程序上下文
     * @param  cause 异常
     * @return void
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        log.info("{}:发生了错误,此连接被关闭。此时连通数量:{}",ctx.channel().id(),
                NettyModBusTcpServer.biDirectionHashMap.size());
    }


    /**
     * 移除通道缓存
     * @param channel
     */
    public void removeChannelCache(Channel channel) {
        //不包含返回
        if (! NettyTcpServer.biDirectionHashMap.containsValue(channel)) {
            return;
        }
        //包含设备信息,移除设备
        NettyTcpServer.biDirectionHashMap.removeByValue(channel);
        String deviceId = NettyTcpServer.biDirectionHashMap.getByValue(channel);
        String key = CacheConstants.GW_DEVICE_CONECT_KEY+deviceId;
        redisService.deleteObject(key);
    }
}

